// Copyright (C) 2006-2009 XL59.com, Gary Hai.  All rights reserved.

#include <v8.h>
#include <pcl.h>
#include <assert.h>
#include "coroutine.h"

namespace nop {

static v8::Persistent<v8::String> cor_status_suspended;
static v8::Persistent<v8::String> cor_status_running;
static v8::Persistent<v8::String> cor_status_normal;
static v8::Persistent<v8::String> cor_status_dead;

Coroutine *Coroutine::running_;
v8::Persistent<v8::ObjectTemplate> Coroutine::coroutine_template_;

Coroutine::~Coroutine() {
  func_.Dispose();
  params_.Dispose();
  context_.Dispose();
  if (!handle_.IsEmpty()) {
    assert(handle_.IsNearDeath());
    handle_->SetInternalField(0, v8::Undefined());
    handle_.Dispose();
    handle_.Clear();
  }
  if (coroutine_) {
    if (coroutine_ != co_current())
      co_delete(coroutine_);
    else
      co_exit();
  }
  coroutine_ = NULL;
  if (Coroutine::running_ == this)
    Coroutine::running_ = NULL;
}

void Coroutine::Initialize(v8::Handle<v8::ObjectTemplate> *tpl) {
  v8::HandleScope handle_scope;
  cor_status_suspended = v8::Persistent<v8::String>::New(v8::String::NewSymbol("suspended"));
  cor_status_running = v8::Persistent<v8::String>::New(v8::String::NewSymbol("running"));
  cor_status_normal = v8::Persistent<v8::String>::New(v8::String::NewSymbol("normal"));
  cor_status_dead = v8::Persistent<v8::String>::New(v8::String::NewSymbol("dead"));
  v8::Handle<v8::ObjectTemplate> coroutine = v8::ObjectTemplate::New();
  coroutine->Set(v8::String::New("create"), v8::FunctionTemplate::New(Coroutine::Create));
  coroutine->Set(v8::String::New("resume"), v8::FunctionTemplate::New(Coroutine::Resume));
  coroutine->Set(v8::String::New("yield"), v8::FunctionTemplate::New(Coroutine::Yield));
  coroutine->Set(v8::String::New("status"), v8::FunctionTemplate::New(Coroutine::Status));
  coroutine->Set(v8::String::New("running"), v8::FunctionTemplate::New(Coroutine::Running));
  if (tpl) {
    (*tpl)->Set(v8::String::New("coroutine"), coroutine);
  } else {
    v8::Local<v8::Object> global = v8::Context::GetCurrent()->Global();
    //TODO: exception handling.
    global->Set(v8::String::New("coroutine"), coroutine->NewInstance());
  }
}

Coroutine* Coroutine::Unwrap(v8::Handle<v8::Value> coroutine) {
  v8::HandleScope handle_scope;
  if (coroutine.IsEmpty() || !coroutine->IsObject()) return NULL;
  v8::Handle<v8::Object> cor = v8::Handle<v8::Object>::Cast(coroutine);
  if (cor->InternalFieldCount() < 1) return NULL;
  return static_cast<Coroutine*>(v8::Handle<v8::External>::Cast(cor->GetInternalField(0))->Value());
}

void Coroutine::SetParameter() {
  v8::HandleScope handle_scope;
  params_.Dispose();
  params_.Clear();
  params_ = v8::Persistent<v8::Value>::New(v8::Undefined());
}

void Coroutine::SetParameter(v8::Handle<v8::Value> arg) {
  v8::HandleScope handle_scope;
  params_.Dispose();
  params_.Clear();
  params_ = v8::Persistent<v8::Value>::New(arg);
}

v8::Handle<v8::Value> Coroutine::Create(v8::Handle<v8::Function> func, v8::Handle<v8::Context> context) {
  v8::HandleScope handle_scope;
  if (!func->IsFunction() || context.IsEmpty()) return v8::Undefined();
  Coroutine *cor = new Coroutine();
  cor->coroutine_ = co_create(CoWrapper, static_cast<void *>(cor), NULL, DEFAULT_STACK_SIZE);
  if (cor->coroutine_ == NULL) {
    delete cor;
    return v8::Undefined();
  }

  cor->context_ = v8::Persistent<v8::Context>::New(context);
  cor->func_ = v8::Persistent<v8::Function>::New(func);
  cor->status_ = cor_status_suspended;

  if (cor->NewInstance())
    return cor->handle_;

  delete cor;
  return v8::Undefined();
}

v8::Handle<v8::Value> Coroutine::Create(const v8::Arguments& args) {
  // Check parameter.
  v8::HandleScope handle_scope;
  v8::Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(args[0]);
  if (!func->IsFunction()) return v8::Undefined();
  v8::Handle<v8::Context> context = v8::Context::GetEntered();
  if (context.IsEmpty()) return v8::Undefined();

  v8::Handle<v8::Value> cor = Coroutine::Create(func, context);
  if (!cor->IsObject())
    return v8::Undefined();
  return cor;
}

v8::Handle<v8::Value> Coroutine::Resume(const v8::Arguments& args) {
  v8::HandleScope handle_scope;

  if (args.Length() < 1 || !args[0]->IsObject()) return v8::Undefined();

  Coroutine *cor = Coroutine::Unwrap(args[0]);
  if (!cor || cor->status_ != cor_status_suspended) return v8::Undefined();

  if (args.Length() > 1)
    cor->SetParameter(args[1]);
  else
    cor->SetParameter();

  Coroutine *running = Coroutine::running_;
  if (running) running->status_ = cor_status_normal;

  co_call(cor->coroutine_);
  if (running) running->status_ = cor_status_running;
  Coroutine::running_ = running;
  return cor->params_;
}

v8::Handle<v8::Value> Coroutine::Yield(const v8::Arguments& args) {
  v8::HandleScope handle_scope;
  Coroutine *cor = Coroutine::running_;
  if (!cor || cor->status_ != cor_status_running) return v8::Undefined();

  if (args.Length() > 0)
    cor->SetParameter(args[0]);
  else
    cor->SetParameter();
  cor->status_ = cor_status_suspended;
  co_resume();
  cor->status_ = cor_status_running;
  Coroutine::running_ = cor;
  return cor->params_;
}

v8::Handle<v8::Value> Coroutine::Running(const v8::Arguments& args) {
  v8::HandleScope handle_scope;
  if (Coroutine::running_) return Coroutine::running_->handle_;
  return v8::Undefined();
}

v8::Handle<v8::Value> Coroutine::Status(const v8::Arguments& args) {
  v8::HandleScope handle_scope;
  Coroutine *cor = Coroutine::Unwrap(args[0]);
  if (!cor) return v8::Undefined();
  return cor->status_;
}

bool Coroutine::NewInstance() {
  // Handle scope for temporary handles.
  v8::HandleScope handle_scope;

  // Fetch the template for creating JavaScript http request wrappers.
  // It only has to be created once, which we do on demand.
  if (coroutine_template_.IsEmpty()) {
    v8::Handle<v8::ObjectTemplate> tpl = v8::ObjectTemplate::New();
    tpl->SetInternalFieldCount(1);
    coroutine_template_ = v8::Persistent<v8::ObjectTemplate>::New(tpl);
  }
  v8::Handle<v8::ObjectTemplate> templ = coroutine_template_;

  // Create an empty wrapper.
  v8::Handle<v8::Object> result = templ->NewInstance();

  // Wrap the raw C++ pointer in an External so it can be referenced
  // from within JavaScript.
  v8::Handle<v8::External> ptr = v8::External::New(this);

  // Store the request pointer in the JavaScript wrapper.
  result->SetInternalField(0, ptr);

  handle_ = v8::Persistent<v8::Object>::New(result);
  handle_.MakeWeak(this, Coroutine::WeakCallback);
  // Return the result through the current handle scope.  Since each
  // of these handles will go away when the handle scope is deleted
  // we need to call Close to let one, the result, escape into the
  // outer handle scope.
  return true;
}

void Coroutine::CoWrapper(void* data) {
  v8::HandleScope handle_scope;
  Coroutine *cor = static_cast<Coroutine *>(data);
  assert(cor);
  // Enter this processor's context so all the remaining operations
  // take place there
  v8::Context::Scope context_scope(cor->context_);
  Coroutine::running_ = cor;

  cor->status_ = cor_status_running;
  v8::Handle<v8::Value> argv[] = {cor->params_};
  v8::Handle<v8::Value> result = cor->func_->Call(cor->func_, 1, argv);
  cor->SetParameter(result);

  cor->status_ = cor_status_dead;
  coroutine_t dead = cor->coroutine_;
  cor->coroutine_ = NULL;
  cor->handle_.Dispose();
}

void Coroutine::WeakCallback (v8::Persistent<v8::Value> value, void *data) {
  Coroutine *obj = static_cast<Coroutine*>(data);
  assert(value == obj->handle_);
  delete obj;
}

}       // namespace nop

int co_launch(void (*comain)(void *), void *arg) {
  coroutine_t coroutine_main = co_create(comain, arg, NULL, DEFAULT_STACK_SIZE);
  co_call(coroutine_main);
  return 0;
}
